/**
 * Copyright [2019] [LiBo/Alex of copyright liboware@gmail.com ]
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hyts.dess.mybatis.plugins.execute;

import com.znlh.demo.mybatisdemo.parser.LocalFieldParser;
import com.znlh.demo.mybatisdemo.parser.LocalInvokeParser;
import com.znlh.demo.mybatisdemo.thread.ThreadContextHolder;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.Properties;

/**
 * @project-name:dess
 * @package-name:com.hyts.dess.mybatis.interceptor
 * @author:LiBo/Alex
 * @create-date:2020-06-15 9:26
 * @copyright:libo-alex4java
 * @email:liboware@gmail.com
 * @description:
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
@Slf4j
public class CustomExecutorInterceptor implements Interceptor {


    private Properties properties;


    /**
     * 拦截器操作处理功能实现
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("进入执行器操作拦截插件");
        CustomExecutorObject customExecutorObject = CustomExecutorUtils.createBean();
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        BoundSql boundSql = null;
        CacheKey cacheKey = null;
        String callType = customExecutorObject.getCondtionCallerType();
        switch (callType){
            case "local":
                 String data = String.valueOf(customExecutorObject.getCondtionValue());
                 if(data.startsWith("@[")){
                     Object value = LocalFieldParser.invoke(LocalFieldParser.parse(data),customExecutorObject.getConditionSubject());
                     customExecutorObject.setCondtionValue(value);
                 }else{
                     Object value = LocalInvokeParser.invoke(LocalInvokeParser.parse(data),customExecutorObject.getConditionSubject(),false);
                     customExecutorObject.setCondtionValue(value);
                 }
                 break;
            case "remote":
                data = String.valueOf(customExecutorObject.getCondtionValue());
                Object value = LocalInvokeParser.invoke(LocalInvokeParser.parse(data),customExecutorObject.getConditionSubject(),true);
                customExecutorObject.setCondtionValue(value);
                break;
        }
        try {
            //4 个参数时
            if(CustomExecutorUtils.isSelectSql(mappedStatement)){
                boundSql = mappedStatement.getBoundSql(parameter);
                MetaObject metaObject = SystemMetaObject.forObject(executor);
                String originalSql = boundSql.getSql();
                log.info("读取获取原始SQL语句信息:{}",originalSql);
                Statement statement = CCJSqlParserUtil.parse(originalSql);
                Select select = (Select) statement;
                SelectBody selectBody = select.getSelectBody();
                if(selectBody instanceof PlainSelect){
                    PlainSelect plainSelect = (PlainSelect) selectBody;
                    if (Objects.nonNull(plainSelect.getWhere())) {
                        log.info("解析操作where条件语句操作服务:{}", plainSelect.getWhere().toString());
                        // 简单的进行控制 未来会考虑复杂场景
                        String newWhereStr = MessageFormat.format(plainSelect.getWhere().toString() + " and {0} {1} {2}",
                                customExecutorObject.getConditonKey(),customExecutorObject.getCondtionToken(),customExecutorObject.getCondtionValue());
                        Expression newWhereObject = (CCJSqlParserUtil.parseCondExpression(newWhereStr));
                        plainSelect.setWhere(newWhereObject);
                        log.info("完成包装或者拼接的sql语句{}",plainSelect.toString());
                        //select.setSelectBody(plainSelect);
                        StatementHandler statementHandler = mappedStatement.getConfiguration().newStatementHandler(executor, mappedStatement, parameter, RowBounds.DEFAULT, null, null);
                        MetaObject metaObject2 = SystemMetaObject.forObject(statementHandler);
                        metaObject2.setValue("delegate.boundSql.sql", plainSelect.toString());
                        //通过反射修改sql语句
                        Field field = boundSql.getClass().getDeclaredField("sql");
                        field.setAccessible(true);
                        field.set(boundSql, plainSelect.toString());
                        //return invocation.proceed();
                        //return doSubSelect(plainSelect,customExecutorObject,metaObject,invocation);
                    }else{
                        //排除 无where条件的
                        return invocation.proceed();
                    }
                }
            }
            // 执行查询操作服务处理
            if(args.length == 4){
                return executor.query(mappedStatement, parameter, rowBounds, resultHandler);
            } else {
                //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
                return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            //TODO 自己要进行的各种处理
            //注：下面的方法可以根据自己的逻辑调用多次，在分页插件中，count 和 page 各调用了一次
        } catch (Exception e) {
            log.error("执行定制化sql拦截器失败！",e);
            return invocation.proceed();
        } finally {
            ThreadContextHolder.remove(CustomExecutorContext.getTHREAD_LOCAL_EXECUTOR_CONTEXT());
        }
    }

    /**
     * 注入或者绑定拦截器到相关的拦截器栈对应的操作处理器功能
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //TODO Spring bean 方式配置时，如果没有配置属性就不会执行下面的 setProperties 方法，就不会初始化，因此考虑在这个方法中做一次判断和初始化
        //TODO https://github.com/pagehelper/Mybatis-PageHelper/issues/26
        return Plugin.wrap(target, this);
    }


    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
        //TODO 可以读取相关System服务的系统参数进行扩展
    }

    /**
     * 执行子查询操作服务控制
     * @param plain
     * @throws JSQLParserException
     */
    private Object doSubSelect(PlainSelect plain, CustomExecutorObject customExecutorObject, MetaObject metaObject, Invocation invocation) throws JSQLParserException, InvocationTargetException, IllegalAccessException {
        FromItem fromItem = plain.getFromItem();
        if(Objects.nonNull(fromItem)){
            Alias alias = fromItem.getAlias();
                if (fromItem instanceof SubSelect) {
                    SubSelect subSelect = (SubSelect) fromItem;
                    if (subSelect.getSelectBody() != null) {
                        SelectBody selectBody = subSelect.getSelectBody();
                        if (selectBody instanceof PlainSelect) {
                            PlainSelect plainSelect = (PlainSelect) selectBody;
                            String newWhereStr = MessageFormat.format(plainSelect.getWhere().toString() + " and {0} {1} {2}",
                                    customExecutorObject.getConditonKey(),customExecutorObject.getCondtionToken(),customExecutorObject.getCondtionValue());
                            Expression newWhereObject = (CCJSqlParserUtil.parseCondExpression(newWhereStr));
                            Expression wheres = (CCJSqlParserUtil.parseCondExpression(newWhereStr));
                            plainSelect.setWhere(wheres);
                            //将增强后的sql放回
                            metaObject.setValue("delegate.boundSql.sql", plain.toString());
                            return invocation.proceed();
                        }
                    }
                }
        }
        return invocation.proceed();
    }
}
